package utils

import (
	"fmt"
	consul "github.com/hashicorp/consul/api"
	"github.com/hashicorp/consul/api/watch"
	"go.uber.org/zap"
	"google.golang.org/grpc/resolver"
	"net"
	"strconv"
)

// ConsulResolverBuilderRegister 注册grpc发现用的consul组件
func ConsulResolverBuilderRegister(consulAddr string) {
	resolver.Register(&ConsulResolverBuilder{
		Target: consulAddr,
	})
}

type ConsulResolverBuilder struct {
	Target string //consul连接地址
}

func (m *ConsulResolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
	r := &ConsulResolver{
		cc:           cc,
		scheme:       target.URL.Scheme,
		svrName:      target.Endpoint(),
		consulTarget: m.Target,
	}
	if err := r.Start(); err != nil {
		return nil, err
	}
	return r, nil
}

func (m *ConsulResolverBuilder) Scheme() string {
	return "consul"
}

type ConsulResolver struct {
	cc           resolver.ClientConn
	scheme       string
	svrName      string
	consulTarget string
	plan         *watch.Plan
}

func (r *ConsulResolver) Start() error {
	//创建监听器
	watchOps := map[string]interface{}{
		"type":        "service",
		"service":     r.svrName,
		"passingonly": true,
	}
	plan, err := watch.Parse(watchOps)
	if err != nil {
		return err
	}
	Logger.Debug("解析consul服务：" + r.svrName)
	plan.HybridHandler = func(index watch.BlockingParamVal, result interface{}) {
		switch msg := result.(type) {
		case []*consul.ServiceEntry:
			address := make([]resolver.Address, 0)
			for i, entry := range msg {
				addr := net.JoinHostPort(entry.Service.Address, strconv.Itoa(entry.Service.Port))
				address = append(address, resolver.Address{Addr: addr})
				Logger.Debug(fmt.Sprintf("consul发现节点%d: %s", i, addr))
			}
			if len(address) > 0 {
				if err = r.cc.UpdateState(resolver.State{Addresses: address}); err != nil {
					Logger.Error("consul grpc UpdateState fail", zap.Error(err))
					r.cc.ReportError(err)
				}
			}
		default:
			Logger.Debug("unknown consul msg", zap.Any("msg", msg))
		}
	}
	go func() {
		if err = plan.RunWithConfig(r.consulTarget, &consul.Config{}); err != nil {
			Logger.Error("consul watch plan RunWithConfig fail", zap.Error(err))
			r.cc.ReportError(err)
		}
	}()

	r.plan = plan
	return nil
}

func (r *ConsulResolver) Close() {
	//关闭监听器
	if r.plan != nil && !r.plan.IsStopped() {
		r.plan.Stop()
	}
}

func (r *ConsulResolver) ResolveNow(options resolver.ResolveNowOptions) {
	Logger.Debug("ResolveNow")
}
